package form

import (
	"errors"
	"fmt"
	"gitee.com/gcom/gbox/tag"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

var invalidPath = errors.New("form: invalid path")

type Decoder struct {
	typeCache *cache
	convCache *converterCache
	// ignore unknown key values, or return error otherwise
	ignoreUnknownKeys bool
	// omit empty value, or convert blank string to destination type otherwise
	omitEmpty bool
}

func NewDecoder() *Decoder {
	cc := newConverterCache()
	for t, c := range builtinTypeConverters {
		cc.tc[t] = c
	}
	return &Decoder{
		typeCache:         newCache("form"),
		convCache:         cc,
		ignoreUnknownKeys: true,
		omitEmpty:         true,
	}
}

// SetTag changes the tag used to locate custom field aliases.
// The default tag is "form".
func (d *Decoder) SetTag(tag string) {
	d.typeCache.tag = tag
}

// ZeroEmpty controls the behaviour when the decoder encounters empty values
// in a map.
// If z is true and a key in the map has the empty string as a value
// then the corresponding struct field is set to the zero value.
// If z is false then empty strings are ignored.
//
// The default value is false, that is empty values do not change
// the value of the struct field.
func (d *Decoder) OmitEmpty(o bool) {
	d.omitEmpty = o
}

// IgnoreUnknownKeys controls the behaviour when the decoder encounters unknown
// keys in the map.
// If i is true and an unknown field is encountered, it is ignored. This is
// similar to how unknown keys are handled by encoding/json.
// If i is false then Decode will return an error. Note that any valid keys
// will still be decoded in to the target struct.
//
// To preserve backwards compatibility, the default value is false.
func (d *Decoder) IgnoreUnknownKeys(i bool) {
	d.ignoreUnknownKeys = i
}

func (d *Decoder) RegisterTypeConverter(t reflect.Type, h ConvertHandler) {
	d.convCache.registerTypeConverter(t, h)
}

func (d *Decoder) RegisterNamedConverter(name string, h ConvertHandler) {
	d.convCache.registerNamedConverter(name, h)
}

func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
	v := reflect.ValueOf(dst)
	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
		return errors.New("form: interface must be a pointer to struct")
	}
	v = v.Elem()
	t := v.Type()
	errs := MultiError{}
	for path, values := range src {
		if parts, err := d.parsePath(path, t); err == nil {
			// check omitEmpty
			omit := d.omitEmpty
			for i := len(parts) - 1; i >= 0; i-- {
				if f := parts[len(parts)-1]; f.field == nil {
					continue
				} else {
					oe := f.field.OmitEmpty
					if oe == 1 {
						omit = true
					} else if oe == -1 {
						omit = false
					}
					break
				}
			}

			if omit {
				values = filterEmptyValues(values)
			}
			if len(values) > 0 {
				if err = d.decodePath(v, parts, values); err != nil {
					errs[path] = err
				}
			}
		} else if !d.ignoreUnknownKeys {
			errs[path] = UnknownKeyError{Key: path}
		}
	}
	if len(errs) > 0 {
		return errs
	}
	return nil
}

func (d *Decoder) decodePath(v reflect.Value, parts []pathPart, value []string) error {
	var e error
	var mp, mk, mv reflect.Value
	for _, p := range parts {
		if mk.IsValid() && mv.IsValid() {
			mp.SetMapIndex(mk, mv)
			mp = reflect.Value{}
			mk = reflect.Value{}
			mv = reflect.Value{}
		}

		v = unwrapAndInitPtrVal(v)
		// check v type must be struct or map
		if p.field != nil {
			v = v.FieldByName(p.path)
			v = unwrapAndInitPtrVal(v)
		} else {
			if v.IsNil() {
				v.Set(reflect.MakeMap(v.Type()))
			}
			mp = v
			mk = reflect.ValueOf(p.path)
			mv = reflect.New(p.typ).Elem()
			v = unwrapAndInitPtrVal(mv)
		}

		if len(p.indices) > 0 {
			v, e = assembleSlice(v, p.indices)
			if e != nil {
				return invalidPath
			}
		}
	}

	if !v.CanSet() {
		if d.ignoreUnknownKeys {
			return nil
		}
		return invalidPath
	}
	part := parts[len(parts)-1]
	field := part.field
	opt := tag.TagOptions{}
	var conv *converterInfo
	if field != nil {
		conv = d.convCache.getConvertHandler(field, part.indices)
		opt = field.Tag
	} else {
		conv = d.convCache.getTypeHandler(part.typ, part.indices)
	}
	if conv != nil {
		err := conv.convert(v, value, opt)
		if err == nil && mk.IsValid() && mv.IsValid() {
			mp.SetMapIndex(mk, mv)
		}
		return err
	}
	return invalidPath
}

func (d *Decoder) parsePath(p string, t reflect.Type) ([]pathPart, error) {
	var s *StructMeta
	var f *FieldMeta
	var err error
	parts := make([]pathPart, 0)
	keys := strings.Split(p, ".")
	for i := 0; i < len(keys); i++ {
		kind := t.Kind()
		if kind != reflect.Struct && kind != reflect.Map {
			return nil, fmt.Errorf("only support to decode struct and map type")
		}
		if kind != reflect.Map {
			if s, err = d.typeCache.Get(t); err != nil {
				return nil, err
			}
		}

		var key = keys[i]
		var indices []int
		if key[len(key)-1] == ']' {
			key, indices, err = parseArrayBracketIndices(key)
			if err != nil {
				return nil, err
			}
		}

		var pp = pathPart{}
		if t.Kind() == reflect.Map {
			pp.path = key
			pp.typ = t.Elem()
		} else {
			if f = s.GetField(key); f == nil {
				return nil, invalidPath
			}
			pp.path = f.Name
			pp.field = f
			pp.typ = f.Typ
		}

		t = indirectPtr(pp.typ)
		kind = t.Kind()
		if indices == nil && (kind == reflect.Slice || kind == reflect.Array) {
			indices, err = parseArrayDotIndices(keys[i+1:])
			if err != nil {
				return nil, err
			}
			i += len(indices)
		}
		pp.indices = indices

		parts = append(parts, pp)
		t, err = getIndexedType(t, len(indices))
		if err != nil {
			return nil, fmt.Errorf("invalid path %s, %s", p, err)
		}
		t = indirectPtr(t)
	}
	return parts, nil
}

func filterEmptyValues(values []string) []string {
	var filtered []string
	for _, v := range values {
		if len(v) > 0 {
			filtered = append(filtered, v)
		}
	}
	return filtered
}

type pathPart struct {
	field   *FieldMeta
	typ     reflect.Type
	path    string // path to the field: walks structs using field names.
	indices []int  // slice or array indices
}

func parseArrayBracketIndices(str string) (field string, indices []int, err error) {
	s := strings.Index(str, "[")
	if s < 0 {
		field = str
		return
	} else {
		field = str[:s]
	}

	var reg = regexp.MustCompile("\\[(\\d+)]")
	match := reg.FindAllStringSubmatch(str[s:], -1)
	for mi := 0; mi < len(match); mi++ {
		ix, _e := strconv.Atoi(match[mi][1])
		if _e != nil {
			err = _e
			return
		}
		indices = append(indices, ix)
	}
	return
}

func parseArrayDotIndices(keys []string) (indices []int, err error) {
	var reg = regexp.MustCompile("^\\d+$")
	for _, k := range keys {
		if !reg.Match([]byte(k)) {
			return
		} else {
			ix, _e := strconv.Atoi(k)
			if _e != nil {
				err = _e
				return
			}
			indices = append(indices, ix)
		}
	}
	return
}

func getIndexedType(t reflect.Type, dim int) (reflect.Type, error) {
	if dim == 0 {
		return t, nil
	}

	for ; dim > 0; dim-- {
		t = indirectPtr(t)
		if t.Kind() != reflect.Slice && t.Kind() != reflect.Array {
			return t, fmt.Errorf("invalid index")
		}
		t = t.Elem()
	}
	return t, nil
}

func unwrapAndInitPtrVal(v reflect.Value) reflect.Value {
	for v.Type().Kind() == reflect.Ptr {
		if v.IsNil() {
			v.Set(reflect.New(v.Type().Elem()))
		}
		v = v.Elem()
	}
	return v
}

func assembleSlice(v reflect.Value, indices []int) (reflect.Value, error) {
	n := &v
	for i, ix := range indices {
		kind := n.Kind()
		if kind != reflect.Array && kind != reflect.Slice {
			return *n, fmt.Errorf("index mismatch: %v, index: %v", v.Type(), indices)
		}
		if n.Len() < ix+1 {
			if kind == reflect.Array {
				return *n, fmt.Errorf("index out of range：%v/%v, index: %d", n.Type(), v.Type(), ix)
			}
			if ix+1 < n.Cap() {
				n.SetLen(ix + 1)
			} else {
				value := reflect.MakeSlice(n.Type(), ix+1, 2*(ix+1))
				reflect.Copy(value, *n)
				if n == &v {
					p := reflect.New(value.Type())
					p.Elem().Set(value)
					v.Set(value)
					n = &v
				} else {
					n.Set(value)
				}
			}
		}

		sv := unwrapAndInitPtrVal(n.Index(ix))
		if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
			n = &sv
		} else if i != len(indices)-1 {
			return *n, fmt.Errorf("index mismatch: %v, index: %v", v.Type(), indices)
		} else {
			return sv, nil
		}
	}
	return *n, nil
}

// UnknownKeyError stores information about an unknown key in the source map.
type UnknownKeyError struct {
	Key string // key from the source map.
}

func (e UnknownKeyError) Error() string {
	return fmt.Sprintf("form: invalid path %q", e.Key)
}

// EmptyFieldError stores information about an empty required field.
type EmptyFieldError struct {
	Key string // required key in the source map.
}

func (e EmptyFieldError) Error() string {
	return fmt.Sprintf("%v is empty", e.Key)
}

// MultiError stores multiple decoding errors.
//
// Borrowed from the App Engine SDK.
type MultiError map[string]error

func (e MultiError) Error() string {
	s := ""
	for _, err := range e {
		s = err.Error()
		break
	}
	switch len(e) {
	case 0:
		return "(0 errors)"
	case 1:
		return s
	case 2:
		return s + " (and 1 other error)"
	}
	return fmt.Sprintf("%s (and %d other errors)", s, len(e)-1)
}

func (e MultiError) merge(errors MultiError) {
	for key, err := range errors {
		if e[key] == nil {
			e[key] = err
		}
	}
}
